from pathlib import Path
from textwrap import dedent
from typing import Literal

import pytest
from plumbum import local

import copier
from copier._user_data import load_answersfile_data

from .helpers import BRACKET_ENVOPS_JSON, SUFFIX_TMPL, build_file_tree, git, git_init


@pytest.fixture(scope="module")
def template_path(tmp_path_factory: pytest.TempPathFactory) -> str:
    root = tmp_path_factory.mktemp("template")
    build_file_tree(
        {
            (root / "api_project" / "api_readme.md"): "",
            (root / "api_project" / "[[ _copier_conf.answers_file ]].tmpl"): (
                "[[ _copier_answers|to_nice_yaml ]]"
            ),
            (root / "conf_project" / "conf_readme.md"): (
                """\
                # Template subdirectory

                This is the template README.
                """
            ),
            (root / "conf_project" / "conf_readme.md.tmpl"): (
                """\
                # Demo subdirectory

                Generated using previous answers `_subdirectory` value.
                """
            ),
            (root / "conf_project" / "[[ _copier_conf.answers_file ]].tmpl"): (
                "[[ _copier_answers|to_nice_yaml ]]"
            ),
            (root / "conf_project" / "[[ filename ]].tmpl"): (
                "[[ filename ]] contents"
            ),
            (root / "copier.yml"): (
                f"""\
                _templates_suffix: {SUFFIX_TMPL}
                _envops: {BRACKET_ENVOPS_JSON}
                choose_subdir:
                    type: str
                    default: conf_project
                    choices:
                        - api_project
                        - conf_project
                _subdirectory: "[[ choose_subdir ]]"
                filename:
                    type: str
                    default: mock_filename
                """
            ),
        }
    )
    with local.cwd(root):
        git_init()
    return str(root)


def test_copy_subdirectory_api_option(template_path: str, tmp_path: Path) -> None:
    copier.run_copy(
        template_path,
        tmp_path,
        defaults=True,
        overwrite=True,
        data={"choose_subdir": "api_project"},
    )
    assert (tmp_path / "api_readme.md").exists()
    assert not (tmp_path / "conf_readme.md").exists()


def test_copy_subdirectory_config(template_path: str, tmp_path: Path) -> None:
    copier.run_copy(template_path, tmp_path, defaults=True, overwrite=True)
    assert (tmp_path / "conf_readme.md").exists()
    assert not (tmp_path / "api_readme.md").exists()


def test_copy_subdirectory_config_no_overwrite(
    template_path: str, tmp_path: Path
) -> None:
    copier.run_copy(template_path, tmp_path, defaults=True, overwrite=False)
    assert (tmp_path / "conf_readme.md").exists()
    assert (tmp_path / "mock_filename").exists()
    assert "mock_filename contents" in (tmp_path / "mock_filename").read_text()
    assert "# Demo subdirectory" in (tmp_path / "conf_readme.md").read_text()
    assert "# Template subdirectory" not in (tmp_path / "conf_readme.md").read_text()
    assert not (tmp_path / "api_readme.md").exists()


def test_update_subdirectory(template_path: str, tmp_path: Path) -> None:
    copier.run_copy(template_path, tmp_path, defaults=True, overwrite=True)

    with local.cwd(tmp_path):
        git_init()

    copier.run_update(dst_path=tmp_path, defaults=True, overwrite=True)
    assert not (tmp_path / "conf_project").exists()
    assert not (tmp_path / "api_project").exists()
    assert not (tmp_path / "api_readme.md").exists()
    assert (tmp_path / "conf_readme.md").exists()


def test_update_subdirectory_from_root_path(
    tmp_path_factory: pytest.TempPathFactory,
) -> None:
    src, dst = map(tmp_path_factory.mktemp, ("src", "dst"))
    with local.cwd(src):
        build_file_tree(
            {
                "copier.yaml": (
                    """\
                    q1:
                        type: str
                        default: a1
                    """
                ),
                "file1.jinja": (
                    """\
                    version 1
                    hello
                    {{ q1 }}
                    bye
                    """
                ),
                "{{ _copier_conf.answers_file }}.jinja": "{{ _copier_answers|to_nice_yaml }}",
            }
        )
        git("init")
        git("add", ".")
        git("commit", "-m1")
        git("tag", "1")
        build_file_tree(
            {
                "file1.jinja": (
                    """\
                    version 2
                    hello
                    {{ q1 }}
                    bye
                    """
                ),
            }
        )
        git("commit", "-am2")
        git("tag", "2")
    with local.cwd(dst):
        build_file_tree({"dst_top_file": "one"})
        git("init")
        git("add", ".")
        git("commit", "-m0")
    copier.run_copy(
        str(src),
        dst / "subfolder",
        vcs_ref="1",
        defaults=True,
        overwrite=True,
        answers_file=".custom.copier-answers.yaml",
    )
    assert (dst / "subfolder" / "file1").read_text() == "version 1\nhello\na1\nbye\n"
    with local.cwd(dst):
        git("add", ".")
        git("commit", "-m1")
        copier.run_update(
            "subfolder",
            defaults=True,
            overwrite=True,
            answers_file=".custom.copier-answers.yaml",
        )
    answers = load_answersfile_data(dst / "subfolder", ".custom.copier-answers.yaml")
    assert answers["_commit"] == "2"
    assert (dst / "subfolder" / "file1").read_text() == "version 2\nhello\na1\nbye\n"


@pytest.mark.parametrize(
    "conflict, readme, expect_reject",
    [
        (
            "rej",
            "upstream version 2\n",
            True,
        ),
        (
            "inline",
            dedent(
                """\
                <<<<<<< before updating
                downstream version 1
                =======
                upstream version 2
                >>>>>>> after updating
                """
            ),
            False,
        ),
    ],
)
def test_new_version_uses_subdirectory(
    tmp_path_factory: pytest.TempPathFactory,
    conflict: Literal["rej", "inline"],
    readme: str,
    expect_reject: bool,
) -> None:
    # Template in v1 doesn't have a _subdirectory;
    # in v2 it moves all things into a subdir and adds that key to copier.yml.
    # Some files change. Downstream project has evolved too. Does that work as expected?
    src, dst = map(tmp_path_factory.mktemp, ("src", "dst"))

    # First, create the template with an initial README
    build_file_tree(
        {
            (src / "README.md"): "upstream version 1",
            (src / "{{_copier_conf.answers_file}}.jinja"): (
                "{{_copier_answers|to_nice_yaml}}"
            ),
        }
    )
    with local.cwd(src):
        git_init("hello template")
        git("tag", "v1")

    # Generate the project a first time, assert the README exists
    copier.run_copy(str(src), dst, defaults=True, overwrite=True)
    assert (dst / "README.md").exists()
    assert load_answersfile_data(dst).get("_commit") == "v1"

    # Start versioning the generated project
    with local.cwd(dst):
        git_init("hello project")

        # After first commit, change the README, commit again
        Path("README.md").write_text("downstream version 1")
        git("commit", "-am", "updated readme")

    # Now change the template
    with local.cwd(src):
        # Update the README
        Path("README.md").write_text("upstream version 2")

        # Create a subdirectory, move files into it
        subdir = Path("subdir")
        subdir.mkdir()
        Path("README.md").rename(subdir / "README.md")
        Path("{{_copier_conf.answers_file}}.jinja").rename(
            subdir / "{{_copier_conf.answers_file}}.jinja"
        )

        # Add the subdirectory option to copier.yml
        Path("copier.yml").write_text(f"_subdirectory: {subdir}")

        # Commit the changes
        git("add", ".", "-A")
        git("commit", "-m", "use a subdirectory now")
        git("tag", "v2")

    # Finally, update the generated project
    copier.run_update(dst_path=dst, defaults=True, overwrite=True, conflict=conflict)
    assert load_answersfile_data(dst).get("_commit") == "v2"

    # Assert that the README still exists, and the conflicts were handled
    # correctly.
    assert (dst / "README.md").exists()

    assert (dst / "README.md").read_text().splitlines() == readme.splitlines()
    assert (dst / "README.md.rej").exists() == expect_reject

    # Also assert the subdirectory itself was not rendered
    assert not (dst / subdir).exists()


def test_new_version_changes_subdirectory(
    tmp_path_factory: pytest.TempPathFactory,
) -> None:
    # Template in v3 changes from one subdirectory to another.
    # Some file evolves also. Sub-project evolves separately.
    # Sub-project is updated. Does that work as expected?
    src, dst = map(tmp_path_factory.mktemp, ("src", "dst"))

    # First, create the template with an initial subdirectory and README inside it
    build_file_tree(
        {
            (src / "copier.yml"): "_subdirectory: subdir1\n",
            (src / "subdir1" / "[[_copier_conf.answers_file]].tmpl"): (
                "[[_copier_answers|to_nice_yaml]]\n"
            ),
            (src / "subdir1" / "README.md"): "upstream version 1\n",
        }
    )
    with local.cwd(src):
        git_init("hello template")

    # Generate the project a first time, assert the README exists
    copier.run_copy(str(src), dst, defaults=True, overwrite=True)
    assert (dst / "README.md").exists()

    # Start versioning the generated project
    with local.cwd(dst):
        git_init("hello project")

        # After first commit, change the README, commit again
        Path("README.md").write_text("downstream version 1\n")
        git("commit", "-am", "updated readme")

    # Now change the template
    with local.cwd(src):
        # Update the README
        Path("subdir1", "README.md").write_text("upstream version 2\n")

        # Rename the subdirectory
        Path("subdir1").rename("subdir2")

        # Update copier.yml to reflect this change
        Path("copier.yml").write_text("_subdirectory: subdir2\n")

        # Commit the changes
        git("add", ".", "-A")
        git("commit", "-m", "changed from subdir1 to subdir2")

    # Finally, update the generated project
    copier.run_copy(
        str(src), dst, defaults=True, overwrite=True, skip_if_exists=["README.md"]
    )

    # Assert that the README still exists, and was NOT force updated
    assert (dst / "README.md").exists()
    assert (dst / "README.md").read_text() == "downstream version 1\n"

    # Also assert the subdirectories themselves were not rendered
    assert not (dst / "subdir1").exists()
    assert not (dst / "subdir2").exists()
